package cuppics;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.junit.Test;

public class Prj20 {

	/**
	 * n! means n × (n − 1) × ... × 3 × 2 × 1
	 * 
	 * For example, 10! = 10 × 9 × ... × 3 × 2 × 1 = 3628800, and the sum of the
	 * digits in the number 10! is 3 + 6 + 2 + 8 + 8 + 0 + 0 = 27.
	 * 
	 * Find the sum of the digits in the number 100!
	 */
	@Test
	public void test() {
		
		System.out.println( Calculator.calculate(100));
	}

	public static class Calculator {

		/**
		 * 1. 数组存取 2. 分解因子部分优化 2 * 5 = 10 , 不影响计算的去除 3. 乘法进一位
		 * 
		 * @param n
		 * @return
		 */
		public static int calculate(int n) {
			int bits = getBits(n);

			int[] data = new int[bits];
			data[bits - 1] = 1; // init

			Map<Long, Integer> primeMap = new HashMap<Long, Integer>();

			IntegerDivisor div = new IntegerDivisor();
			for (int i = 2; i <= n; i++) {
				div.divisor(i);
				mergerPrimeMap(div.primeMap, primeMap);
				div.clear();
			}

			optimizeMap(primeMap);
			System.out.println( IntegerDivisor.print_Map(primeMap));
			for (Entry<Long, Integer> entry : primeMap.entrySet()) {
				Long prime = entry.getKey();
				int times = entry.getValue();
				for (int i = 1; i <= times; i++) {

					for (int bitIter = 0; bitIter < bits; bitIter++) {
						data[bitIter] *= prime;
					}

					for (int bitIter = bits - 1; bitIter > 0; bitIter--) {
						data[bitIter - 1] += (data[bitIter] / 10);
						data[bitIter] %= 10;
					}
				}
			}

			int sum = 0;
			for (int i = 0; i < bits; i++) {
				sum += data[i];
				System.out.println("data[" + i +"]=" + data[i]);
			}

			return sum;

		}

		private static void mergerPrimeMap(Map<Long, Integer> divPrimeMap,
				Map<Long, Integer> primeMap) {
			
			for( Entry<Long, Integer> entry : divPrimeMap.entrySet()){
				if( !primeMap.containsKey(entry.getKey())){
					primeMap.put( entry.getKey(), 0);
				}
				int time = primeMap.get( entry.getKey()) + entry.getValue();
				primeMap.put( entry.getKey(),  time);
			}
		}

		private static void optimizeMap(Map<Long, Integer> primeMap){
		
			if( primeMap.containsKey(2L) && primeMap.containsKey(5L)){
				
				int minSubtract = Math.min( primeMap.get(2L), primeMap.get(5L));
				
				int time2 = primeMap.get(2L);
				int time5 = primeMap.get(5L);
				
				primeMap.put((long) 2,  time2 - minSubtract);
				primeMap.put((long) 5,  time5 - minSubtract);
				
				if( primeMap.get(2L) == 0){
					primeMap.remove(2L);
				}
				if( primeMap.get(5L) == 0){
					primeMap.remove(5L);
				}
			}
		}
		
		public static int getBits(int n) {
			double bits = 0;
			for (int i = 1; i <= 100; i++) {
				bits += Math.log(i) / Math.log(2);
			}
			System.out.println(bits);

			return (int) (Math.ceil(bits) + 1);
		}
	}

	public static class IntegerDivisor {

		public Map<Long, Integer> primeMap = new HashMap<Long, Integer>();
		public List<Long> primeList = new ArrayList<Long>();

		public void clear() {
			primeMap.clear();
			primeList.clear();
		}

		public void divisor(long num) {

			if (num <= 1)
				return;

			long prime = getPrime(
					num,
					primeList.size() == 0 ? 2
							: primeList.get(primeList.size() - 1));
			if (prime < 0) {
				primeMap.put(num, 1);
				primeList.add(num);
				return;
			} else {

				primeList.add(prime);
				int count = 0;
				do {

					count += 1;
					num = num / prime;
				} while (num % prime == 0);

				primeMap.put(prime, count);

				divisor(num);

			}

		}

		private long getPrime(long num, long start) {

			for (long i = start; i <= Math.sqrt(num); i++) {
				if (num % i == 0) {
					return i;
				}
			}
			return -1;
		}

		@Override
		public String toString() {

			return print_Map( this.primeMap);
		}

		public static String print_Map(Map<Long, Integer> primeMap) {
			StringBuilder sb = new StringBuilder();
			for (Entry<Long, Integer> entry : primeMap.entrySet()) {
				sb.append(entry.getKey().toString() + "="
						+ entry.getValue().toString() + "\n");
			}
			return sb.toString();
		}

		public Long getLargestPrime() {
			return primeList.get(primeList.size() - 1);
		}

	}

}
